package org.mtmi.ui.controls.midi;

import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.mtmi.io.IControlIO;
import org.mtmi.io.controls.MidiControllerIO;
import org.mtmi.midi.MidiManager;
import org.mtmi.tuio.listeners.SingleTapListener;
import org.mtmi.tuio.listeners.TUIOTapEvent;
import org.mtmi.ui.GraphicUtil;
import org.mtmi.ui.IAdaptable;
import org.mtmi.ui.IMG;
import org.mtmi.ui.controls.Control;
import org.mtmi.ui.controls.ITouchableControl;
import org.mtmi.ui.controls.IValueGenerator;
import org.mtmi.ui.controls.TouchControlEvent;
import org.mtmi.ui.controls.listeners.ControlDisposedListener;
import org.mtmi.ui.controls.listeners.DisposeEvent;
import org.mtmi.ui.controls.listeners.ValueChangedListener;
import org.mtmi.ui.controls.listeners.ValueEvent;
import org.mtmi.ui.drawables.IDrawable;
import org.mtmi.ui.edit.IControlEditor;
import org.mtmi.ui.layouts.LayoutData;
import org.mtmi.ui.scenes.COLOR;
import org.mtmi.ui.scenes.FontManager;
import org.mtmi.ui.scenes.Scene;

import rwmidi.MidiOutput;

/**
 * A Midi Controller is associated to:
 * <ul>
 * <li>an ouput
 * <li>a channel
 * <li>a midi controller value
 * <li>a value
 * </ul> 
 * It' behave like a button, when hit, the control message is send.<p>
 * Value can also be connected to Control generating values, message is send every time value changes.
 */
public class MidiController extends Control 
  implements IMidiControl, ITouchableControl, SingleTapListener, ValueChangedListener, ControlDisposedListener
{
	private static final int SP = 5;
	private static final int MG = 10;
	
	private int midi_output;
	private int midi_channel;
	private int midi_controller;
	private int midi_value;
	private IValueGenerator ctr_value; //might be null
	private String ctr_value_connection_id;
	private Font font;
	
	private IDrawable /*d_output, d_channel, */d_controller, d_value;
	
	public MidiController(Scene scene, int _flags) {
		super(scene, _flags);
		//default:
		midi_output = 0;
		midi_channel = 0;
		midi_controller=7; //general volume
		midi_value=127;
		font = FontManager.getInstance().getToolBarFont();
		
		setBackground( COLOR.grey30 );
		setForeground( COLOR.grey80 );
		
	//	d_output = IMG.GetD( IMG.I_MIDI_PORT );
	//	d_channel = IMG.GetD( IMG.I_MIDI_CHANNEL );
		d_controller= IMG.GetD( IMG.I_MIDI_CONTROLLER );
		d_value= IMG.GetD( IMG.I_MIDI_VALUE );
		
		setSize( computeSize( new LayoutData(scene) ) );
	}
	
	@Override
	public int getMidiOutput() { return midi_output; }
	@Override
	public void setMidiOutput(int output) { midi_output = output; }
	
	@Override
	public int getMidiChannel() { return midi_channel; }
	@Override
	public void setMidiChannel(int ch) { midi_channel = ch; }
	
	public int getMidiController() { return midi_controller;}
	public void setMidiController( int mc ) { midi_controller=mc;}
	
	public int getMidiValue() { return midi_value;}
	public void setMidiValue( int v ) { midi_value=v;}
	
	/** @return the control generating value (might be null for constant value */
	public IValueGenerator getValueController() { return ctr_value; } 
	public String getValueControllerConnectionId() { return ctr_value_connection_id; } 
	
	public void setValueController( IValueGenerator ctr, String cid ) { 
		if( ctr_value!=null ) {
			if( ctr_value instanceof IAdaptable ) {
				Control c = (Control)((IAdaptable)ctr_value).getAdapter( Control.class, null );
				c.removeControlDisposedListener( this );
			}
			ctr_value.removeValueChangedListener( this );
		}
		ctr_value = ctr;
		ctr_value_connection_id = cid;
		if( ctr_value != null ) {
			if( ctr_value instanceof IAdaptable ) {
				Control c = (Control)((IAdaptable)ctr_value).getAdapter( Control.class, null );
				c.addControlDisposedListener( this );
			}
			ctr_value.addValueChangedListener( this );
			updateValue(); //assign current value
		}
	}
	
	@Override
	public Point computeSize(LayoutData l) {
		
		String tmax="888"; //$NON-NLS-1$
		
		l.gc.setFont( font );
		
		int lh = l.gc.textExtent("Hp").y;//$NON-NLS-1$
		lh = Math.max( 24, lh ); //24 pixel height min
		
		Point stmax = l.gc.textExtent( tmax );
		
	//	Point so = d_output.computeSize( lh, lh );
	//	Point sc = d_channel.computeSize( lh, lh );
		Point st = d_controller.computeSize( lh, lh );
		Point sv = d_value.computeSize( lh, lh );
		
		//ouput and channel on first line:
	//	int w1 = so.x+SP+stmax.x+SP+sc.x+SP+stmax.x;
	//	int h1 = Math.max( so.y, Math.max( stmax.y, sc.y ));
		
		//controller and value on 2nd line
		int w2 = st.x+SP+stmax.x+SP+sv.x+SP+stmax.x;
		int h2 = Math.max( st.y, Math.max( stmax.y, sv.y ));
		
		int wx = w2;//Math.max( w1, w2 );
		int th = /*h1+SP+*/h2;
		
		return new Point( MG+wx+MG, MG+th+MG );
	}

	@Override
	public boolean containsTouch(TouchControlEvent e) {
		if( getScene().isEditing() ) {
			return GraphicUtil.rectContains( location, size, e.x, e.y );
		} else {
			return false;
		}
	}

	@Override
	public void paint(GC gc) 
	{
		if( !getScene().isEditing() ) return;
		
		gc.setForeground( getForeground() );
		gc.setBackground( getBackground() );
		gc.setLineWidth( borderWidth );
		gc.fillRectangle( location.x, location.y, size.x, size.y );
		int b2 = borderWidth/2;
		gc.drawRectangle( location.x+b2, location.y+b2, size.x-borderWidth, size.y-borderWidth );
		
		gc.setFont( font );
		int lh = Math.max( 24, gc.textExtent("Hp").y ); //$NON-NLS-1$
		
	//	Point so = d_output.computeSize( lh, lh );
	//	Point sc = d_channel.computeSize( lh, lh );
		Point st = d_controller.computeSize( lh, lh );
		Point sv = d_value.computeSize( lh, lh );
		
		Point stmax = gc.textExtent("888"); //$NON-NLS-1$
		
		//1st line
	/*	int h1 = Math.max( so.y, Math.max( stmax.y, sc.y ));
		int x = location.x+MG;
		int y = location.y+MG;
		int yl = y+(h1-so.y)/2;
		d_output.paint( gc, x, yl, so.x, so.y, IDrawable.NORMAL );
		x += so.x+SP;
		
		String txt = Integer.toString(midi_output);
		Point stxt = gc.textExtent( txt );
		yl = y+(h1-stxt.y)/2;
		gc.drawText( txt, x, yl, true );
		x += stmax.x+SP;
		
		yl = y+(h1-sc.y)/2;
		d_channel.paint( gc, x, yl, sc.x, sc.y, IDrawable.NORMAL );
		x += sc.x+SP;
		
		txt = Integer.toString(midi_channel);
		stxt = gc.textExtent( txt );
		yl = y+(h1-stxt.y)/2;
		gc.drawText( txt, x, yl, true ); 
	*/	
		
		//2nd line:
		int x = location.x+MG;
		// y += h1+SP;
		int y = location.y+MG;
		
		int h2 = Math.max( st.y, Math.max( stmax.y, sv.y ));
		
		int yl = y+(h2-st.y)/2;
		d_controller.paint( gc, x, yl, st.x, st.y, IDrawable.NORMAL );
		x += st.x+SP;
		
		String txt = Integer.toString(midi_controller);
		Point stxt = gc.textExtent( txt );
		yl = y+(h2-stxt.y)/2;
		gc.drawText( txt, x, yl, true );
		x += stmax.x+SP;
		
		yl = y+(h2-sv.y)/2;
		d_value.paint( gc, x, yl, sv.x, sv.y, IDrawable.NORMAL );
		x += sv.x+SP;
		
		txt = Integer.toString(midi_value);
		stxt = gc.textExtent( txt );
		yl = y+(h2-stxt.y)/2;
		gc.drawText( txt, x, yl, true );
		
	}

	@Override
	public void tapDown(TUIOTapEvent event) {
		sendMessage();
	}

	@Override
	public void tapUp(TUIOTapEvent event) {
	}

	@Override
	public void valueChanged( ValueEvent event ) {
		if( event.control == ctr_value ) {
			updateValue();
		}
	}
	
	private void updateValue() {
		float k = ctr_value.getValueF();
		//TODO: range 0..127 for all ???			
		midi_value = Math.round( 127*k );
		sendMessage();
		redraw();
	}

	@Override
	public void controlDisposed(DisposeEvent event) {
		if( event.control == ctr_value ) {
			ctr_value = null;
		}
	}
	
	private void sendMessage()
	{
		MidiOutput mo = MidiManager.getInstance().getMidiOutput( midi_output );
		mo.sendController( midi_channel, midi_controller, midi_value );
	}

	@Override
	public Object getAdapter( Class<?> clazz, Object arg ) {
		if( clazz == IControlEditor.class ) {
			return new MidiControllerControlEditor( this );
		}
		if( clazz == IControlIO.class ) {
			return new MidiControllerIO();
		}
		return super.getAdapter( clazz, arg );
	}
}
